package com.ktkj.init.utils;

import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * 压缩、解压 -> 工具类
 * 更多示例,可见官网:http://commons.apache.org/proper/commons-compress/examples.html
 * 总体说明:如果有同名的文件,那么压缩/解压后的文件将会覆盖原来的文件
 *
 * @author yanlixiong @ktkj.cn
 * @date :2021-03-19 11:51:49
 * @DATE 2018年9月25日 下午12:26:47
 */
public class CompressionAndDecompressionUtils {
    /**
     * The entry point of application.
     *
     * @param args the input arguments
     * @author :yanlixiong@xx.cn
     * @date :2021-03-21 15:43:47
     */
    public static void main(String[] args) {
        try {
           /* String path = "E:\\home\\test\\file-all\\";
            String commonName = "123";
            // tar打包压缩
            String[] filesPathArray = {path + commonName + ".txt"};
            String resultFilePath = path + commonName + ".tar";
            tarCompression(filesPathArray, resultFilePath);
            // tar拆包解压
            tarDecompression(resultFilePath, path + "tar");


            // 对.tar文件进行gzip压缩 至 .tar.gz
            gzipCompression(resultFilePath, resultFilePath + ".gz");
            // 解压.tar.gz文件 至 .tar文件
            gzipDecompression(resultFilePath + ".gz", path + "gz");


            // zip打包压缩
            String zipResultFilePath = path + commonName + ".zip";
            zipCompression(filesPathArray, zipResultFilePath);
            // zip拆包解压
            zipDecompression(zipResultFilePath, path + "zip");


            // 对.tar文件进行bzip2压缩 至 .tar.bz2
            bz2Compression(resultFilePath, resultFilePath + ".bz2");
            // 解压.tar.bz2文件 至 .tar文件
            bz2Decompression(resultFilePath + ".bz2", path + "bz2");*/

            //把一个目录打包到一个指定的zip文件中
            compressFoldToZip("E:\\home\\test\\file-all\\zip-test", "E:\\home\\test\\file-all\\AABB.zip", "");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * tar打包压缩
     *
     * @param filesPathArray 要压缩的文件的全路径(数组)
     * @param resultFilePath 压缩后的文件全文件名(.tar)
     * @return the boolean
     * @throws Exception the exception
     * @author :yanlixiong@xx.cn
     * @date :2021-03-21 15:43:47
     */
    public static boolean tarCompression(String[] filesPathArray, String resultFilePath) throws Exception {
        System.out.println(" tarCompression -> Compression start!");
        FileOutputStream fos = null;
        TarArchiveOutputStream taos = null;
        try {
            fos = new FileOutputStream(new File(resultFilePath));
            taos = new TarArchiveOutputStream(fos);
            for (String filePath : filesPathArray) {
                BufferedInputStream bis = null;
                FileInputStream fis = null;
                try {
                    File file = new File(filePath);
                    TarArchiveEntry tae = new TarArchiveEntry(file);
                    // 此处指明 每一个被压缩文件的名字,以便于解压时TarArchiveEntry的getName()方法获取到的直接就是这里指定的文件名
                    // 以(左边的)GBK编码将file.getName()“打碎”为序列,再“组装”序列为(右边的)GBK编码的字符串
                    tae.setName(new String(file.getName().getBytes("GBK"), "GBK"));
                    taos.putArchiveEntry(tae);
                    fis = new FileInputStream(file);
                    bis = new BufferedInputStream(fis);
                    int count;
                    byte[] data = new byte[1024];
                    while ((count = bis.read(data, 0, 1024)) != -1) {
                        taos.write(data, 0, count);
                    }
                } finally {
                    taos.closeArchiveEntry();
                    if (bis != null) {
                        bis.close();
                    }
                    if (fis != null) {
                        fis.close();
                    }
                }
            }
        } finally {
            if (taos != null) {
                taos.close();
            }
            if (fos != null) {
                fos.close();
            }

        }
        System.out.println(" tarCompression -> Compression end!");
        return true;
    }

    /**
     * tar拆包解压
     *
     * @param decompressFilePath 要被解压的压缩文件 全路径
     * @param resultDirPath      解压文件存放绝对路径(目录)
     * @return the boolean
     * @throws Exception the exception
     * @author :yanlixiong@xx.cn
     * @date :2021-03-21 15:43:48
     */
    public static boolean tarDecompression(String decompressFilePath, String resultDirPath) throws Exception {
        System.out.println(" tarDecompression -> Decompression start!");
        TarArchiveInputStream tais = null;
        FileInputStream fis = null;
        try {
            fileMkdirs(resultDirPath);
            File file = new File(decompressFilePath);
            fis = new FileInputStream(file);
            tais = new TarArchiveInputStream(fis);
            TarArchiveEntry tae = null;
            while ((tae = tais.getNextTarEntry()) != null) {
                BufferedOutputStream bos = null;
                FileOutputStream fos = null;
                try {
                    String dir = resultDirPath + File.separator + tae.getName(); // tar档中文件
                    File dirFile = new File(dir);
                    fos = new FileOutputStream(dirFile);
                    bos = new BufferedOutputStream(fos);
                    int count;
                    byte[] data = new byte[1024];
                    while ((count = tais.read(data, 0, 1024)) != -1) {
                        bos.write(data, 0, count);
                    }
                } finally {
                    if (bos != null) {
                        bos.close();
                    }
                    if (fos != null) {
                        fos.close();
                    }
                }
            }
        } finally {
            if (tais != null) {
                tais.close();
            }
            if (fis != null) {
                fis.close();
            }
        }
        System.out.println(" tarDecompression -> Decompression end!");
        return true;
    }

    /**
     * 对.tar文件进行gzip压缩
     * 说明:我们一般先把多个文件tar打包为一个,然后再使用gzip进行压缩; 进而获得形如“abc.tar.gz”这样的压缩文件
     * 注:这里暂时不再深入学习,以后有闲暇时间可深入了解如何压缩多个文件等
     * 注:如果明确知道解压后的是什么类型的文件;那么可以直接指定解压后的文件类型(实际上也需要这么做);
     * .tar.gz 解压后就是.tar文件,所以我们在解压时,给出的解压后的文件的全路径名就是以.tar结尾的
     *
     * @param filePath       要被压缩的压缩文件 全路径
     * @param resultFilePath 压缩后的文件(全文件名 .gz)
     * @return the boolean
     * @throws IOException the io exception
     * @author :yanlixiong@xx.cn
     * @date :2021-03-21 15:43:48
     */
    public static boolean gzipCompression(String filePath, String resultFilePath) throws IOException {
        System.out.println(" gzipCompression -> Compression start!");
        InputStream fin = null;
        BufferedInputStream bis = null;
        FileOutputStream fos = null;
        BufferedOutputStream bos = null;
        GzipCompressorOutputStream gcos = null;
        try {
            fin = Files.newInputStream(Paths.get(filePath));
            bis = new BufferedInputStream(fin);
            fos = new FileOutputStream(resultFilePath);
            bos = new BufferedOutputStream(fos);
            gcos = new GzipCompressorOutputStream(bos);
            byte[] buffer = new byte[1024];
            int read = -1;
            while ((read = bis.read(buffer)) != -1) {
                gcos.write(buffer, 0, read);
            }
        } finally {
            if (gcos != null) {
                gcos.close();
            }
            if (bos != null) {
                bos.close();
            }
            if (fos != null) {
                fos.close();
            }
            if (bis != null) {
                bis.close();
            }
            if (fin != null) {
                fin.close();
            }
        }
        System.out.println(" gzipCompression -> Compression end!");
        return true;
    }

    /**
     * 解压对.tar.gz文件至 .tar文件
     * 说明:我们一般都是对.tar.gz文件进行gzip解压; 进而获得形如.tar文件;再进行解压
     *
     * @param compressedFilePath 要被解压的压缩文件 全路径
     * @param resultDirPath      解压文件存放绝对路径(目录)
     * @return the boolean
     * @throws IOException the io exception
     * @author :yanlixiong@xx.cn
     * @date :2021-03-21 15:43:48
     */
    public static boolean gzipDecompression(String compressedFilePath, String resultDirPath) throws IOException {
        System.out.println(" gzipDecompression -> Compression start!");
        BufferedOutputStream bos = null;
        GzipCompressorInputStream gcis = null;
        try {
            fileMkdirs(resultDirPath);
            File file = new File(compressedFilePath);
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
            String fileName = file.getName().substring(0, file.getName().lastIndexOf("."));
            String finalName = resultDirPath + File.separator + fileName;
            bos = new BufferedOutputStream(new FileOutputStream(finalName));
            gcis = new GzipCompressorInputStream(bis);

            byte[] buffer = new byte[1024];
            int read = -1;
            while ((read = gcis.read(buffer)) != -1) {
                bos.write(buffer, 0, read);
            }
        } finally {
            if (bos != null) {
                bos.close();
            }
            if (gcis != null) {
                gcis.close();
            }
        }
        System.out.println(" gzipDecompression -> Compression end!");
        return true;
    }

    /**
     * zip压缩(注:与tar类似)
     *
     * @param filesPathArray 要压缩的文件的全路径(数组)
     * @param resultFilePath 压缩后的文件全文件名(.tar)
     * @return the boolean
     * @throws Exception the exception
     * @author :yanlixiong@xx.cn
     * @date :2021-03-21 15:43:48
     */
    public static boolean zipCompression(String[] filesPathArray, String resultFilePath) throws Exception {
        System.out.println(" zipCompression -> Compression start!");
        ZipArchiveOutputStream zaos = null;
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(new File(resultFilePath));
            zaos = new ZipArchiveOutputStream(fos);
            for (String filePath : filesPathArray) {
                FileInputStream fis = null;
                BufferedInputStream bis = null;
                try {
                    File file = new File(filePath);
                    // 第二个参数如果是文件全路径名,那么压缩时也会将路径文件夹也缩进去;
                    // 我们之压缩目标文件,而不压缩该文件所处位置的相关文件夹,所以这里我们用file.getName()
                    ZipArchiveEntry zae = new ZipArchiveEntry(file, file.getName());
                    zaos.putArchiveEntry(zae);
                    fis = new FileInputStream(file);
                    bis = new BufferedInputStream(fis);
                    int count;
                    byte[] data = new byte[1024];
                    while ((count = bis.read(data, 0, 1024)) != -1) {
                        zaos.write(data, 0, count);
                    }
                } finally {
                    zaos.closeArchiveEntry();
                    if (bis != null) {
                        bis.close();
                    }
                    if (fis != null) {
                        fis.close();
                    }
                }

            }
        } finally {
            if (zaos != null) {
                zaos.close();
            }
            if (fos != null) {
                fos.close();
            }
        }
        System.out.println(" zipCompression -> Compression end!");
        return true;
    }

    /**
     * 把一个目录打包到一个指定的zip文件中
     *
     * @param dirPath
     * @param zipPath
     * @param entryPath 压缩内文件逻辑路径。如static/
     */
    public static void compressFoldToZip(String dirPath, String zipPath, String entryPath) {
        System.out.println(" compressFoldToZip -> Compression start!");
        if (!entryPath.endsWith(File.separator) && StringUtils.isNotBlank(entryPath)) {
            entryPath += File.separator;
        }
        ZipArchiveOutputStream out = null;
        try {
            out = new ZipArchiveOutputStream(new BufferedOutputStream(new FileOutputStream(new File(zipPath))));
            out.setEncoding("UTF-8");
            compressFoldToZip(out, dirPath, entryPath);
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(out);
        }
        System.out.println(" compressFoldToZip -> Compression end!");
    }

    /**
     * 把一个目录打包到一个指定的zip文件中
     *
     * @param out
     * @param dirPath   目录路径
     * @param entryPath zip中文件的逻辑路径
     */
    private static void compressFoldToZip(ZipArchiveOutputStream out, String dirPath, String entryPath) {
        InputStream ins = null;
        File dir = new File(dirPath);
        File[] files = dir.listFiles();
        if (files == null || files.length < 1) {
            return;
        }
        try {
            for (int i = 0; i < files.length; i++) {
                // 判断此文件是否是一个文件夹
                if (files[i].isDirectory()) {
                    if (files[i].listFiles().length > 0) {
                        compressFoldToZip(out, files[i].getAbsolutePath(), entryPath + files[i].getName() + File.separator);
                    } else {
                        addFileToZip(files[i], out, entryPath);
                    }
                } else {
                    addFileToZip(files[i], out, entryPath);
                }
            }
            out.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(ins);
        }
    }

    private static void addFileToZip(File file, ZipArchiveOutputStream out, String entryPath) {
        InputStream ins = null;
        try {

            String path = entryPath + file.getName();
            if (file.isDirectory()) {
                path = formatDirPath(path); //为了在压缩文件中包含空文件夹
            }
            ZipArchiveEntry entry = new ZipArchiveEntry(path);
            entry.setTime(file.lastModified());
            // entry.setSize(files[i].length());
            out.putArchiveEntry(entry);
            if (!file.isDirectory()) {
                ins = new BufferedInputStream(new FileInputStream(file.getAbsolutePath()));
                IOUtils.copy(ins, out);
            }
            out.closeArchiveEntry();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(ins);
        }
    }

    private static String formatDirPath(String dir) {
        if (!dir.endsWith(File.separator)) {
            dir += File.separator;
        }
        return dir;
    }

    /**
     * zip解压(注:与tar类似)
     *
     * @param zipPath       zip文件全路径
     * @param outputDirPath 解压缩后文件保存全路径
     * @author :yanlixiong@xx.cn
     * @date :2021-03-21 15:43:48
     */
    public static void zipDecompression(String zipPath, String outputDirPath) {
        System.out.println(" zipDecompression -> Compression start!");
        File zip = new File(zipPath);
        File outputDir = new File(outputDirPath);

        FileOutputStream out = null;
        InputStream in = null;
        //读出文件数据
        ZipFile zipFileData = null;
        ZipFile zipFile = null;
        try {
            fileMkdirs(outputDirPath);
            //若目标保存文件位置不存在
            if (outputDir != null) {
                if (!outputDir.exists()) {
                    outputDir.mkdirs();
                }
            }
            zipFile = new ZipFile(zip.getPath(), Charset.forName("GBK"));
            Enumeration<? extends ZipEntry> entries = zipFile.entries();
            //处理创建文件夹
            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();
                String filePath = "";

                if (outputDir == null) {
                    filePath = zip.getParentFile().getPath() + File.separator + entry.getName();
                } else {
                    filePath = outputDir.getPath() + File.separator + entry.getName();
                }
                File file = new File(filePath);
                File parentFile = file.getParentFile();
                if (!parentFile.exists()) {
                    parentFile.mkdirs();
                }
                if (parentFile.isDirectory()) {
                    continue;
                }
            }
            zipFileData = new ZipFile(zip.getPath(), Charset.forName("GBK"));
            Enumeration<? extends ZipEntry> entriesData = zipFileData.entries();
            while (entriesData.hasMoreElements()) {
                ZipEntry entry = entriesData.nextElement();
                in = zipFile.getInputStream(entry);
                String filePath = "";
                if (outputDir == null) {
                    filePath = zip.getParentFile().getPath() + File.separator + entry.getName();
                } else {
                    filePath = outputDir.getPath() + File.separator + entry.getName();
                }
                File file = new File(filePath);
                if (file.isDirectory()) {
                    continue;
                }
                out = new FileOutputStream(filePath);
                int len = -1;
                byte[] bytes = new byte[1024];
                while ((len = in.read(bytes)) != -1) {
                    out.write(bytes, 0, len);
                }
                out.flush();
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
                in.close();
                zipFile.close();
                zipFileData.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        System.out.println(" zipDecompression -> Compression end!");
    }

    /**
     * 对.tar文件进行bzip2压缩
     * 说明:我们一般先把多个文件tar打包为一个,然后再使用bzip2进行压缩; 进而获得形如“abc.tar.bz2”这样的压缩文件
     * 注:如果明确知道解压后的是什么类型的文件;那么可以直接指定解压后的文件类型(实际上也需要这么做);
     * .tar.bz2 解压后就是.tar文件,所以我们在解压时,给出的解压后的文件的全路径名就是以.tar结尾的
     *
     * @param filePath       要被压缩的压缩文件 全路径
     * @param resultFilePath 压缩后的文件(全文件名 .bz2)
     * @return the boolean
     * @throws Exception the exception
     * @author :yanlixiong@xx.cn
     * @date :2021-03-21 15:43:48
     */
    public static boolean bz2Compression(String filePath, String resultFilePath) throws Exception {
        System.out.println(" bz2Compression -> Compression start!");
        InputStream fin = null;
        BufferedInputStream bis = null;
        FileOutputStream fos = null;
        BufferedOutputStream bos = null;
        BZip2CompressorOutputStream gcos = null;
        try {
            fin = Files.newInputStream(Paths.get(filePath));
            bis = new BufferedInputStream(fin);
            fos = new FileOutputStream(resultFilePath);
            bos = new BufferedOutputStream(fos);
            gcos = new BZip2CompressorOutputStream(bos);
            byte[] buffer = new byte[1024];
            int read = -1;
            while ((read = bis.read(buffer)) != -1) {
                gcos.write(buffer, 0, read);
            }
        } finally {
            if (gcos != null) {
                gcos.close();
            }
            if (bos != null) {
                bos.close();
            }
            if (fos != null) {
                fos.close();
            }
            if (bis != null) {
                bis.close();
            }
            if (fin != null) {
                fin.close();
            }
        }
        System.out.println(" bz2Compression -> Compression end!");
        return true;
    }

    /**
     * 解压对.tar.bz2文件至 .tar文件
     * 说明:我们一般都是对.tar.bz2文件进行bzip2解压; 进而获得形如.tar文件;再进行解压
     *
     * @param compressedFilePath 要被解压的压缩文件 全路径
     * @param resultDirPath      解压文件存放绝对路径(目录)
     * @return the boolean
     * @throws IOException the io exception
     * @author :yanlixiong@xx.cn
     * @date :2021-03-21 15:43:48
     */
    public static boolean bz2Decompression(String compressedFilePath, String resultDirPath) throws IOException {
        System.out.println(" bz2Decompression -> Compression start!");
        BufferedOutputStream bos = null;
        BZip2CompressorInputStream gcis = null;
        try {
            fileMkdirs(resultDirPath);
            File file = new File(compressedFilePath);
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
            String fileName = file.getName().substring(0, file.getName().lastIndexOf("."));
            String finalName = resultDirPath + File.separator + fileName;
            bos = new BufferedOutputStream(new FileOutputStream(finalName));
            gcis = new BZip2CompressorInputStream(bis);
            byte[] buffer = new byte[1024];
            int read = -1;
            while ((read = gcis.read(buffer)) != -1) {
                bos.write(buffer, 0, read);
            }
        } finally {
            if (bos != null) {
                bos.close();
            }
            if (gcis != null) {
                gcis.close();
            }
        }
        System.out.println(" bz2Decompression -> Compression end!");
        return true;
    }

    /**
     * File mkdirs.
     *
     * @param path the path
     * @author :yanlixiong@xx.cn
     * @date :2021-03-21 15:43:48
     */
    private static void fileMkdirs(String path) {
        File file = new File(path);
        if (!file.exists()) {
            file.mkdirs();
        }
    }

}
